﻿using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using RuoVea.ExConfig;
using RuoVea.ExJwtBearer;
using RuoVea.OAuthServer.Configs;
using RuoVea.OAuthServer.Model;
using RuoVea.OAuthServer.Server;
using System.Text.Json;
using System.Web;

namespace RuoVea.OAuthServer;

/// <summary>
/// OAuthServiceSetup
/// </summary>
public static class OAuthServiceSetup
{
    /// <summary>
    /// 
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddOAuthServerSetup<T>(this IServiceCollection services) where T : IOAuthServers
    {
        services.AddSingleton<IOAuthServers>();
        services.AddSingleton<IOAuthServers, T>();
        services.Configure<List<OAuthServerConfig>>(AppSettings.GetSection("OAuthServer"));
        return services;
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="app"></param>
    /// <param name="policy"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"></exception>
    public static WebApplication UseOAuthServerUri(this WebApplication app, string policy = null)
    {
        #region authorize
        RouteHandlerBuilder oauthAuthorize = app.MapGet("/oauth/authorize", (HttpContext ctx, IDataProtectionProvider _provider, IOptions<List<OAuthServerConfig>> oauthServerConfigs) =>
            {
                var oauthConfigs = oauthServerConfigs.Value;

                if (oauthConfigs is null)
                    throw new ArgumentNullException("Has Not Set OAuthServer Config Or No Use " + nameof(AddOAuthServerSetup));

                var result = HttpUtility.ParseQueryString(ctx.Request.QueryString.ToString());

                string client_id = "",
                    code_challenge = "",
                    code_challenge_method = "",
                    redirect_uri = "",
                    state = "",
                    code_verifier = "";

                foreach (var key in result.AllKeys)
                {
                    if (key == "client_id") client_id = result[key];
                    else if (key == "redirect_uri") redirect_uri = result[key];

                    else if (key == "code_verifier") code_verifier = result[key];
                    else if (key == "code_challenge") code_challenge = result[key];
                    else if (key == "code_challenge_method") code_challenge_method = result[key];

                    else if (key == "state") state = result[key];
                }
                // TODO 判断信息的合法性
                OAuthServerConfig? authConfig = oauthConfigs.FirstOrDefault(x => x.ClientId == client_id);
                string callback = $"{redirect_uri}?code=err&state={state}&iss={client_id}";

                if (authConfig is null)
                {
                    Console.WriteLine($"没有找到client_id={client_id}的OAuthServer配置");
                    ctx.Response.Redirect(callback);
                    return false;
                }

                if (string.IsNullOrWhiteSpace(authConfig.ClientUri) || redirect_uri.Contains(authConfig.ClientUri) == false)
                {
                    Console.WriteLine($"ClientUri={authConfig.ClientUri} 没有配置或者配置的地址和回调的地址不一致");
                    ctx.Response.Redirect(callback);
                    return false;
                }

                if (string.IsNullOrWhiteSpace(authConfig.ClientId))
                {
                    Console.WriteLine($"没有找到client_id={client_id}");
                    ctx.Response.Redirect(callback);
                    return false;
                }

                if (string.IsNullOrWhiteSpace(authConfig.ClientSecret) || authConfig.ClientSecret.Length < 8)
                {
                    Console.WriteLine($"ClientSecret={authConfig.ClientSecret} 没有配置或者配置的长度太短");
                    ctx.Response.Redirect(callback);
                    return false;
                }

                // todo oauth可以是用客户端密钥替代
                var protector = _provider.CreateProtector($"oauth{authConfig.ClientSecret}");

                var code = new
                {
                    ClientId = client_id,
                    CodeChallenge = code_challenge,
                    CodeChallengeMethod = code_challenge_method,
                    RedirectUri = redirect_uri,
                    iss = ctx.User?.Claims?.FirstOrDefault(c => c.Type == "sub")?.Value,
                    Expiry = DateTime.Now.AddMinutes(5)
                };
                var codeStr = protector.Protect(JsonSerializer.Serialize(code));
                ctx.Response.Redirect($"{redirect_uri}?code={codeStr}&state={state}&iss={client_id}");
                return true;
            });

        if (string.IsNullOrWhiteSpace(policy))
            oauthAuthorize.RequireAuthorization();
        else
            oauthAuthorize.RequireAuthorization(new AuthorizeAttribute() { Policy = policy });
        #endregion

        #region token
        app.MapPost("/oauth/token", async (HttpContext ctx,
            IDataProtectionProvider _provider,
            IJwtHelper _jwtHelper,
            IOAuthServers oauthUtils,
            IOptions<List<OAuthServerConfig>> oauthServerConfigs) =>
        {
            var oauthConfigs = oauthServerConfigs.Value;

            using var bodyReader = new StreamReader(ctx.Request.Body);
            var requestBody = await bodyReader.ReadToEndAsync(); // 一次性读取整个正文

            string grantType = "", code = "", redirect_uri = "", codeVerify = "", client_id = "", client_secret = "";
            foreach (var part in requestBody.Split('&'))
            {
                var subParts = part.Split('=');
                var key = subParts[0];
                var value = subParts[1];
                if (key == "grant_type") grantType = value;
                else if (key == "code") code = value;
                else if (key == "client_id") client_id = value;
                else if (key == "client_secret") client_secret = value;
                else if (key == "redirect_uri") redirect_uri = value;
                else if (key == "code_verifier") codeVerify = value;
            }
            //authorization_code
            // TODO 判断信息的合法性
            OAuthServerConfig? authConfig = oauthConfigs.FirstOrDefault(x => x.ClientId == client_id);

            string callback = $"{redirect_uri}?code=err&iss={client_id}";

            if (authConfig is null)
            {
                Console.WriteLine($"没有找到client_id={client_id}的OAuthServer配置");
                ctx.Response.Redirect(callback);
                return null;
            }

            if ("authorization_code".Equals(code, StringComparison.OrdinalIgnoreCase))
            {
                Console.WriteLine($"code={code}不匹配 authorization_code");
                ctx.Response.Redirect(callback);
                return null;
            }

            if (string.IsNullOrWhiteSpace(authConfig.ClientUri) || System.Web.HttpUtility.UrlDecode(redirect_uri).Contains(authConfig.ClientUri) == false)
            {
                Console.WriteLine($"ClientUri={authConfig.ClientUri} 没有配置或者配置的地址和回调的地址不一致");
                ctx.Response.Redirect(callback);
                return null;
            }

            if (string.IsNullOrWhiteSpace(authConfig.ClientId))
            {
                Console.WriteLine($"没有找到client_id={client_id}");
                ctx.Response.Redirect(callback);
                return null;
            }

            if (string.IsNullOrWhiteSpace(authConfig.ClientSecret) || authConfig.ClientSecret.Length < 8 || string.Compare(authConfig.ClientSecret, client_secret, StringComparison.OrdinalIgnoreCase) != 0)
            {
                Console.WriteLine($"ClientSecret={authConfig.ClientSecret} 没有配置或者配置的长度太短");
                ctx.Response.Redirect(callback);
                return null;
            }

            // todo oauth可以是用客户端密钥替代
            var protect = _provider.CreateProtector($"oauth{authConfig.ClientSecret}");
            var codestr = protect.Unprotect(code);
            var authCode = JsonSerializer.Deserialize<CodeInfoModel>(codestr);

            if (!oauthUtils.ValidateCode(authCode, codeVerify))
            {
                return null;
            }
            string accessToken = oauthUtils.IssueToken(authCode.iss, authConfig.Score);

            var authToken = new
            {
                id_token = RuoVea.ExIdGen.IdGenerator.IdStr(),
                token_type = "Bearer",
                access_token = accessToken,
                expires_in = DateTime.Now.AddSeconds(120).ToString(),

                refresh_token = _jwtHelper.GenerateRefreshToken(accessToken)
            };
            return authToken;
        }); 
        #endregion

        return app;
    }
}
